import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SemanticAnalysis {

	/** O Node de topo do ficheiro Yal. */
	private Node topNode = null;

	/** O Scope de topo do ficheiro Yal. */
	private Scope topScope = null;

	/**
	 * Analisa uma arvore que representa um ficheiro.
	 * 
	 * @param rootNode
	 *            O Node de topo de um ficheiro Yal
	 */
	public SemanticAnalysis(Node rootNode) {
		topNode = rootNode;
		topScope = new Scope(null, rootNode);
	}

	/**
	 * Verifica se o ficheiro contem funcoes com o mesmo nome.
	 * 
	 * @throws RepeatFunctionException
	 *             Se houver uma funcao com o mesmo nome.
	 */
	public void verificarFuncoesRepetidas() throws RepeatFunctionException {

		if (topScope.getFunctionsInScope() != null) {
			for (Function func1 : topScope.getFunctionsInScope()) {
				int nOcurrencias = 0;

				for (Function func2 : topScope.getFunctionsInScope()) {
					if (func1.getFunctionName().equals(func2.getFunctionName())) {
						nOcurrencias++;
					}
				}

				if (nOcurrencias > 1) {
					throw new RepeatFunctionException(func1.getFunctionName());
				}
			}
		}
	}

	/**
	 * <p>
	 * Verifica se todas as chamadas a funcoes que retornam variaveis do tipo
	 * "int" estao a ser usadas em assigns.
	 * </p>
	 * 
	 * <p>
	 * Por exemplo:
	 * </p>
	 * 
	 * <pre>
	 *  function a = f1() {
	 *      a=2;
	 *  }
	 *  
	 *  function f2() {
	 *      f1();  // semantic error: line 9, function "f1" returns scalar
	 *  }
	 * </pre>
	 * 
	 * <p>
	 * E um erro, pois a chamada a <i>f1()</i> deveria fazer assign a uma
	 * variavel. Ou seja, o codigo deveria ser semelhante a:
	 * </p>
	 * 
	 * <pre>
	 *  function a = f1() {
	 *      a=2;
	 *  }
	 *  
	 *  function f2() {
	 *      foo = f1();
	 *  }
	 * </pre>
	 * 
	 * @throws WrongFunctionReturnException
	 *             Se houver chamadas a funcoes que devolvam "int" e nao estejam
	 *             a ser atribuidas a variaveis
	 */
	public void verificarFuncoesComScalarReturnFazemAssign() throws WrongFunctionReturnException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		// Obter funcoes que fazem return de scalar
		List<Function> funcoesComReturnScalar = new LinkedList<Function>();

		for (Function func : topScope.getFunctionsInScope()) {
			if (func.getReturnType() != null) {
				if (func.getReturnType().equals("int")) {
					funcoesComReturnScalar.add(func);
				}
			}
		}

		// Obter todos os nodes to tipo Call
		List<Node> callNodes = getAllNodes("Call");

		if (callNodes != null) {
			for (Node node : callNodes) {
				// Obter nome da funcao que esta a ser chamada
				String nomeFuncao = null;

				for (Node childNode : node.jjtGetChildren()) {
					if (childNode.toString().equals("NomeClasse")) {
						SimpleNode childSN = (SimpleNode) childNode;
						nomeFuncao = childSN.val;
					}
				}

				// Verificar se o nome corresponde a uma funcao que devolve
				// Scalar
				for (Function func : funcoesComReturnScalar) {
					if (func.getFunctionName().equals(nomeFuncao)) {
						if (!node.jjtGetParent().toString().equals("RightAssignSide")) {
							throw new WrongFunctionReturnException("int", "void");
						}
					}
				}
			}
		}
	}

	/**
	 * Devolve todos os Nodes da arvore do ficheiro Yal que tenham como nome o
	 * valor passado em <i>nodeType</i>.
	 * 
	 * @param nodeType
	 *            Uma string que indica o nome dos nodes que vao ser devolvidos
	 * @return Uma lista de Node em que cada Node tem como nome o valor passado
	 *         em <i>nodeType</i>
	 */
	private List<Node> getAllNodes(String nodeType) {
		return getAllNodes(nodeType, topNode);
	}

	/**
	 * Devolve todos os Nodes da arvore do ficheiro Yal que estejam a um nivel
	 * inferior de <i>currentNode</i> e que tenham como nome o valor passado em
	 * <i>nodeType</i>.
	 * 
	 * @param nodeType
	 *            Uma string que indica o nome dos nodes que vao ser devolvidos
	 * @param currentNode
	 *            O Node topo da pesquisa
	 * @return Uma lista de Node em que cada Node tem como nome o valor passado
	 *         em <i>nodeType</i>
	 */
	private List<Node> getAllNodes(String nodeType, Node currentNode) {

		List<Node> nodesEncontrados = null;

		for (Node childNode : currentNode.jjtGetChildren()) {
			if (childNode.toString().equals(nodeType)) {
				if (nodesEncontrados == null)
					nodesEncontrados = new LinkedList<Node>();
				nodesEncontrados.add(childNode);
			}

			if (childNode.jjtGetNumChildren() != 0) {
				List<Node> nodesEncontradoMaisFundos = getAllNodes(nodeType, childNode);

				if (nodesEncontradoMaisFundos != null) {
					if (nodesEncontrados == null)
						nodesEncontrados = new LinkedList<Node>();

					for (Node nodeEnc : nodesEncontradoMaisFundos) {
						nodesEncontrados.add(nodeEnc);
					}
				}
			}
		}

		return nodesEncontrados;
	}

	/**
	 * Verifica se todas as chamadas a funcoes de Yal que nao devolvem nenhuma
	 * variavel nao estao a ser usadas em assigns.
	 * 
	 * Por exemplo:
	 * 
	 * <pre>
	 * function f1() {
	 * 	a = 2;
	 * }
	 * 
	 * function f2() {
	 * 	b = f1(); // semantic error: line 10, function 'f1' returns void
	 * }
	 * </pre>
	 * 
	 * @throws WrongFunctionReturnException
	 *             Se existe uma chamada a uma funcao que devolve null, mas esta
	 *             a ser assigned a uma variavel
	 */
	public void verificarFuncoesSemReturnoNaoFazemAssign() throws WrongFunctionReturnException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		// Obter funcoes que devolvem void
		List<Function> funcoesSemReturn = new LinkedList<Function>();

		for (Function func : topScope.getFunctionsInScope()) {
			if (func.getReturnType() == null) {
				funcoesSemReturn.add(func);
			}
		}

		// Obter todos os nodes to tipo Call
		List<Node> callNodes = getAllNodes("Call");

		if (callNodes != null) {
			for (Node node : callNodes) {
				// Obter nome da funcao que esta a ser chamada
				String nomeFuncao = null;

				for (Node childNode : node.jjtGetChildren()) {
					if (childNode.toString().equals("NomeClasse")) {
						SimpleNode childSN = (SimpleNode) childNode;
						nomeFuncao = childSN.val;
					}
				}

				// Verificar se o nome corresponde a uma funcao que devolve
				// Void
				for (Function func : funcoesSemReturn) {
					if (func.getFunctionName().equals(nomeFuncao)) {
						if (node.jjtGetParent().toString().equals("RightAssignSide")) {
							throw new WrongFunctionReturnException("void",	"not-void");
						}
					}
				}
			}
		}
	}

	/**
	 * <p>
	 * Verifica se todas as funcoes chamadas estao no modulo actual.
	 * </p>
	 * 
	 * <p>
	 * Nota: Assume-se que a chamada a funcoes de outros modulos existem, ou
	 * seja, o seguinte codigo e considerado um erro semantico:
	 * </p>
	 * 
	 * <pre>
	 * module err4 {
	 *     function f1() {
	 *         a=2;
	 *     }
	 * 
	 *     function f2() {
	 *         f3();  // semantic error: line 9, function 'f3' is not included in this module
	 *     }
	 * }
	 * </pre>
	 * 
	 * Mas este codigo nao e considerado erro semantico:
	 * 
	 * <pre>
	 * module err {
	 *     function main() {
	 *         foo.bar(); // Assume-se que bar() existe no modulo foo
	 *     }
	 * }
	 * </pre>
	 * 
	 * @throws FunctionNotFoundInModuleException
	 *             Se houver uma chamada a uma funcao que nao esteja definida
	 *             neste modulo
	 */
	public void verificarSeFuncaoExisteEmModulo()	throws FunctionNotFoundInModuleException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		// Obter todos os nodes to tipo Call
		List<Node> callNodes = getAllNodes("Call");

		if (callNodes == null) {
			return;
		}

		// Obter todos os nodes to tipo IDFunction_com_return e
		// IDFunction_sem_return
		List<Node> idFunctionSemReturnNodes = getAllNodes("IDFunction_sem_return");
		List<Node> idFunctionComReturnNodes = getAllNodes("IDFunction_com_return");
		List<Node> idFunctionNodes = new LinkedList<Node>();

		if (idFunctionSemReturnNodes != null) {
			for (Node functionNode : idFunctionSemReturnNodes) {
				idFunctionNodes.add(functionNode);
			}
		}
		if (idFunctionComReturnNodes != null) {
			for (Node functionNode : idFunctionComReturnNodes) {
				idFunctionNodes.add(functionNode);
			}
		}

		for (Node callNode : callNodes) {
			// Obter o nome da funcao a ser invocada
			String nomeFuncao = null;

			// Indica se a funcao pertence a outra class
			boolean isExternalFunction = false;

			for (Node childNode : callNode.jjtGetChildren()) {
				if (childNode.toString().equals("NomeClasse")) {
					SimpleNode childSN = (SimpleNode) childNode;
					nomeFuncao = childSN.val;
				} else if (childNode.toString().equals("NomeMetodo")) {
					isExternalFunction = true;
				}
			}

			if (!isExternalFunction) {
				boolean existsFunctionWithName = false;

				for (Node idFunctionNode : idFunctionNodes) {
					SimpleNode idFunctionSN = (SimpleNode) idFunctionNode;
					if (idFunctionSN.val.equals(nomeFuncao)) {
						existsFunctionWithName = true;

						// So pode exister uma funcao com o mesmo nome
						break;
					}
				}

				if (!existsFunctionWithName) {
					throw new FunctionNotFoundInModuleException(nomeFuncao);
				}
			}
		}
	}

	/**
	 * <p>
	 * Verifica se existem chamadas com argumentos a funcoes que <b>nao</b>
	 * recebem argumentos.
	 * </p>
	 * 
	 * <p>
	 * Exemplo:
	 * </p>
	 * 
	 * <pre>
	 * module err5 {
	 *     function f1() {
	 *         a=2;
	 *     }
	 * 
	 *     function f2(a) {
	 *         f1(a);  // semantic error: line 12, function 'f1' does not have arguments
	 *     }
	 * }
	 * </pre>
	 * 
	 * @throws WrongFunctionPrototypeException
	 *             Se existe uma chamada com argumentos a uma funcao que nao
	 *             receba argumentos
	 */
	public void verificarArgumentosDasFuncoes() throws WrongFunctionPrototypeException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		// Obter funcoes que nao tem argumentos
		List<Function> funcoesSemArgumentos = new LinkedList<Function>();

		for (Function function : topScope.getFunctionsInScope()) {
			if (function.getArguments() == null) {
				funcoesSemArgumentos.add(function);
			}
		}

		// Obter uma lista de nodes do tipo Call
		List<Node> callNodes = getAllNodes("Call");

		for (Node callNode : callNodes) {
			// Obter o nome da funcao a ser invocada
			String nomeFuncao = null;

			// Indica se a funcao pertence a outra class
			boolean isExternalFunction = false;

			// Indica se a funcao esta a ser chamada com argumentos
			boolean hasArgumentList = false;

			for (Node childNode : callNode.jjtGetChildren()) {
				if (childNode.toString().equals("NomeClasse")) {
					SimpleNode childSN = (SimpleNode) childNode;
					nomeFuncao = childSN.val;
				} else if (childNode.toString().equals("NomeMetodo")) {
					isExternalFunction = true;
				} else if (childNode.toString().equals("ArgumentList")) {
					hasArgumentList = true;
				}
			}

			if (!isExternalFunction && hasArgumentList) {
				// Ver se este Call chama uma funcao sem argumentos
				for (Function function : funcoesSemArgumentos) {
					if (function.getFunctionName().equals(nomeFuncao)) {
						throw new WrongFunctionPrototypeException("Funcao " + nomeFuncao + " chamada com argumento quando nao recebe nenhum");
					}
				}
			}
		}
	}

	/**
	 * <p>
	 * Verificar se a chamada a funcoes tem o mesmo numero de argumentos com os
	 * quais as funcoes estao definidas.
	 * </p>
	 * 
	 * <p>
	 * Exemplo:
	 * </p>
	 * 
	 * <pre>
	 * module err7 {
	 *     function b=f1(a) {
	 *         b=a;
	 *     }
	 * 
	 *     function f2(a, b,c) {
	 *         a=f1(c, b);  // semantic error: line 10, function 'f1' has only one argument
	 *     }
	 * }
	 * </pre>
	 * 
	 * @throws WrongFunctionPrototypeException
	 *             Se existe uma funcao que receba seja chamada com o numero
	 *             errado de argumentos
	 */
	public void verificarNumeroDeArgumentosEmFuncoes() throws WrongFunctionPrototypeException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		// Obter uma lista de nodes do tipo Call
		List<Node> callNodes = getAllNodes("Call");

		for (Node callNode : callNodes) {
			// Obter o nome da funcao a ser invocada
			String nomeFuncao = null;

			// Indica se a funcao pertence a outra class
			boolean isExternalFunction = false;

			// Argumentos com que a funcao esta a ser chamada
			Node[] argumentList = null;

			for (Node childNode : callNode.jjtGetChildren()) {
				if (childNode.toString().equals("NomeClasse")) {
					SimpleNode childSN = (SimpleNode) childNode;
					nomeFuncao = childSN.val;
				} else if (childNode.toString().equals("NomeMetodo")) {
					isExternalFunction = true;
				} else if (childNode.toString().equals("ArgumentList")) {
					argumentList = childNode.jjtGetChildren();
				}
			}

			if (!isExternalFunction) {
				for (Function function : topScope.getFunctionsInScope()) {
					if (function.getArguments() != null && argumentList != null) {
						if (function.getArguments().size() != argumentList.length) {
							// TODO: Fazer a mensagem de erro
							throw new WrongFunctionPrototypeException(nomeFuncao);
						}
					} else if (function.getArguments() != null && argumentList == null) {
						// TODO: Fazer a mensagem de erro
						throw new WrongFunctionPrototypeException(nomeFuncao);
					} else if (function.getArguments() == null && argumentList != null) {
						// TODO: Fazer a mensagem de erro
						throw new WrongFunctionPrototypeException(nomeFuncao);
					}
				}
			}
		}
	}

	/**
	 * <p>
	 * Verifica se os argumentos de uma chamada a uma funcao sao do mesmo tipo
	 * que os parametros dessa funcao.
	 * </p>
	 * 
	 * <p>
	 * Exemplo:
	 * </p>
	 * 
	 * <pre>
	 * module err9 {
	 *     b=10;
	 * 
	 *     function f2(a, c) {
	 *         a=f1(c, b);  // semantic error: line 6, second argument of function 'f1' must be an array
	 *     }
	 * 	
	 *     function b=f1(a, c[]) {
	 *         b=a+c[0];
	 *     }
	 * }
	 * </pre>
	 * 
	 * @throws WrongFunctionPrototypeException
	 *             Se existe uma chamada a uma funcao que cujos argumentos nao
	 *             sejam do mesmo tipo que os parametros da funcao
	 */
	public void verificarTipoDeArgumentosEmFuncoes() throws WrongFunctionPrototypeException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		Map<Node, Scope> nodeScope = obterScopeDosNodes("Call");

		for (Map.Entry<Node, Scope> callNode : nodeScope.entrySet()) {
			// TODO: Fazer a verificacao de ser uma funcao externa

			List<String> argumentsType = new LinkedList<String>();
			String functionName = null;

			for (Node childNode : callNode.getKey().jjtGetChildren()) {
				if (childNode.toString().equals("ArgumentList")) {
					// Obter o tipo de argumentos que estao a ser usados nesta
					// chamada

					for (Node grandChildNode : childNode.jjtGetChildren()) {
						if (grandChildNode.toString().equals("IDArgument")) {
							SimpleNode grandChildNodeSN = (SimpleNode) grandChildNode;

							// Tipo deste argumento
							String argumentType = callNode.getValue().lookup(grandChildNodeSN.val).getVariableType();
							argumentsType.add(argumentType);
						}
					}
				} else if (childNode.toString().equals("NomeClasse")) {
					SimpleNode childNodeSN = (SimpleNode) childNode;
					functionName = childNodeSN.val;
				}
			}

			for (Function function : topScope.getFunctionsInScope()) {
				if (function.getFunctionName().equals(functionName)) {
					for (int k = 0; k != function.getArgumentsInOrder().size(); k++) {
						if (!function.getArgumentsInOrder().get(k).equals(argumentsType.get(k))) {
							// TODO: Fazer a mensagem de erro
							throw new WrongFunctionPrototypeException(functionName);
						}
					}
				}
			}
		}
	}

	/**
	 * Obtem um Map em que cada Node da arvore que representa o ficheiro Yal (do
	 * tipo <i>nodeType</i>) mapeia para o Scope que esta activo nesse Node.
	 * 
	 * @param nodeType
	 *            Indica o nome dos Nodes que se quer obter
	 * @return Um Map em que cada Node mapeia para o seu Scope que esta activo
	 */
	private Map<Node, Scope> obterScopeDosNodes(String nodeType) {
		Map<Node, Scope> scopesDeTodosNodes = obterScopeDosNodes(topNode, topScope);

		Map<Node, Scope> scopesDosNodesPedidos = null;

		for (Map.Entry<Node, Scope> nodeScope : scopesDeTodosNodes.entrySet()) {
			SimpleNode nodeSN = (SimpleNode) nodeScope.getKey();

			if (nodeSN.toString().equals(nodeType)) {
				if (scopesDosNodesPedidos == null)
					scopesDosNodesPedidos = new HashMap<Node, Scope>();
				
				scopesDosNodesPedidos.put(nodeScope.getKey(), nodeScope.getValue());
			}
		}

		return scopesDosNodesPedidos;
	}

	/**
	 * <p>
	 * Obtem um Map em que cada Node que se encontra a um nivel inferior do Node
	 * <i>currentNode</i> da arvore do ficheiro YAL esta mapeado com o seu
	 * Scope.
	 * </p>
	 * 
	 * <p>
	 * A forma normal de utilizar esta funcao e:
	 * </p>
	 * 
	 * <pre>
	 * obterScopeDosNodes(NodeDeTopoDaArvore, ScopeDeTopoDoFicheiro);
	 * </pre>
	 * 
	 * <p>
	 * o que obtem um Map em que todos os nos da arvore mapeiam para o seu
	 * Scope.
	 * </p>
	 * 
	 * @param currentNode
	 *            O Node que vai ser analisado
	 * @param currentScope
	 *            O Scope a que <i>currentNode</i> pertence
	 * @return Um Map em que cada Node mapeia para o seu Scope
	 */
	private Map<Node, Scope> obterScopeDosNodes(Node currentNode, Scope currentScope) {
		Map<Node, Scope> nodesEncontrados = null;

		int enconteredWhiles = 0;
		int enconteredIfs = 0;

		for (Node childNode : currentNode.jjtGetChildren()) {
			Scope hasNewScope = null;

			if (childNode.toString().equals("WhileBody")) {
				if (nodesEncontrados == null)
					nodesEncontrados = new HashMap<Node, Scope>();

				nodesEncontrados.put(childNode, currentScope.getChildrenScopeByName("while" + (enconteredWhiles + 1)));
				hasNewScope = currentScope.getChildrenScopeByName("while" + (enconteredWhiles + 1));
				enconteredWhiles++;
			} else if (childNode.toString().equals("IfBody")) {
				if (nodesEncontrados == null)
					nodesEncontrados = new HashMap<Node, Scope>();

				nodesEncontrados.put(childNode, currentScope.getChildrenScopeByName("if" + (enconteredIfs + 1)));
				hasNewScope = currentScope.getChildrenScopeByName("if" + (enconteredIfs + 1));
				enconteredIfs++;
			} else if (childNode.toString().equals("ElseBody")) {
				if (nodesEncontrados == null)
					nodesEncontrados = new HashMap<Node, Scope>();

				nodesEncontrados.put(childNode, currentScope.getChildrenScopeByName("else" + enconteredIfs));
				hasNewScope = currentScope.getChildrenScopeByName("else" + enconteredIfs);
			} else if (childNode.toString().equals("Function")) {
				String functionName = null;
				Node functionBody = null;

				for (Node grandChildNode : childNode.jjtGetChildren()) {
					if (grandChildNode.toString().equals("IDFunction_com_return")) {
						SimpleNode grandChildNodeSN = (SimpleNode) grandChildNode;
						functionName = grandChildNodeSN.val;
					} else if (grandChildNode.toString().equals("IDFunction_sem_return")) {
						SimpleNode grandChildNodeSN = (SimpleNode) grandChildNode;
						functionName = grandChildNodeSN.val;
					} else if (grandChildNode.toString().equals("FunctionBody")) {
						functionBody = grandChildNode;
					}
				}

				// Obter o scope desta nova funcao
				for (Function function : currentScope.getFunctionsInScope()) {
					if (function.getFunctionName().equals(functionName)) {
						hasNewScope = function.getFunctionScope();
					}
				}
				nodesEncontrados.put(childNode, hasNewScope);
			} else {
				if (nodesEncontrados == null)
					nodesEncontrados = new HashMap<Node, Scope>();

				nodesEncontrados.put(childNode, currentScope);
			}

			if (childNode.jjtGetNumChildren() != 0) {
				Map<Node, Scope> nodesEncontradosMaisFundos = null;

				if (hasNewScope != null) {
					nodesEncontradosMaisFundos = obterScopeDosNodes(childNode, hasNewScope);
				} else {
					nodesEncontradosMaisFundos = obterScopeDosNodes(childNode, currentScope);
				}

				if (nodesEncontradosMaisFundos != null) {
					for (Map.Entry<Node, Scope> nodeScope : nodesEncontradosMaisFundos.entrySet()) {
						nodesEncontrados.put(nodeScope.getKey(), nodeScope.getValue());
					}
				}
			}
		}

		return nodesEncontrados;
	}

	/**
	 * <p>
	 * Verifica se todos os assigns que ocorrem no ficheiro e que recorram ao
	 * uso de funcoes para calcular valores estao coerentes.
	 * </p>
	 * 
	 * <p>
	 * Ou seja, impede que se tente guardar numa variavel do tipo "int" o
	 * retorno de uma funcao que retorna uma variavel do tipo "int[]".
	 * </p>
	 * 
	 * <p>
	 * Exemplo:
	 * </p>
	 * 
	 * <pre>
	 * module err10 {
	 *     function b[]=f1(a) {
	 *         b=[100];
	 *         b[0]=1;
	 *         b[99]=1;
	 *     }
	 * 
	 *     function a=f2(c) {
	 *         a=f1(c);  // semantic error: line 10, return of function 'f1' is an array
	 *     }
	 * }
	 * </pre>
	 * 
	 * @throws WrongFunctionReturnException
	 *             Se existir chamadas a funcoes que sejam guardadas em
	 *             variaveis que nao sao iguais ao tipo de retorno da funcao
	 */
	public void verificarTipoDeRetornoDasFuncoes() throws WrongFunctionReturnException {
		if (topScope.getFunctionsInScope() == null) {
			return;
		}

		Map<Node, Scope> assignNodeScopes = obterScopeDosNodes("Assign");

		for (Map.Entry<Node, Scope> nodeScope : assignNodeScopes.entrySet()) {

			String functionName = null;
			String assignVariableName = null;

			for (Node childNode : nodeScope.getKey().jjtGetChildren()) {
				if (childNode.toString().equals("RightAssignSide")) {
					for (Node grandChild : childNode.jjtGetChildren()) {
						if (grandChild.toString().equals("Call")) {
							for (Node greatGrandChild : grandChild.jjtGetChildren()) {
								if (greatGrandChild.toString().equals("NomeClasse")) {
									SimpleNode greatGrandChildSN = (SimpleNode) greatGrandChild;
									functionName = greatGrandChildSN.val;
								}

							}
						}
					}
				}

				if (childNode.toString().equals("LeftAssignSide")) {
					for (Node grandChild : childNode.jjtGetChildren()) {
						if (grandChild.toString().equals("ScalarAccess")) {
							for (Node greatGrandChild : grandChild.jjtGetChildren()) {
								if (greatGrandChild.toString().equals("IDScalarAccess")) {
									SimpleNode greatGrandChildSN = (SimpleNode) greatGrandChild;
									assignVariableName = greatGrandChildSN.val;
								}
							}
						}
					}
				}
			}
			for (Function function : topScope.getFunctionsInScope()) {
				if (function.getFunctionName().equals(functionName)) {
					if (!function.getReturnType().equals(nodeScope.getValue().lookup(assignVariableName).getVariableType())) {
						throw new WrongFunctionReturnException(function.getReturnName(),
								nodeScope.getValue().lookup(assignVariableName).getVariableType());
					}
				}
			}
		}
	}
}

class RepeatFunctionException extends Exception {
	private static final long serialVersionUID = 5420702640070473217L;

	/** Nome da funcao que esta definida mais do que uma vez. */
	public String nomeFuncao = null;

	/**
	 * Uma excepcao que representa uma funcao definida mais do que uma vez num
	 * ficheiro Yal.
	 * 
	 * @param nome
	 *            O nome da funcao que esta definida mais do que uma vez
	 */
	public RepeatFunctionException(String nome) {
		nomeFuncao = nome;
	}
}

class NonExistantReturnException extends Exception {
	private static final long serialVersionUID = -2227164044612020799L;

	/** Tipo de retorno da funcao */
	public String tipoRetorno = null;

	public NonExistantReturnException(String retorno) {
		tipoRetorno = retorno;
	}
}

class WrongFunctionReturnException extends Exception {
	private static final long serialVersionUID = 6792827258635572784L;

	/**
	 * <p>
	 * Tipo de retorno da funcao.
	 * </p>
	 * 
	 * <p>
	 * So pode ter os valores "int" ou "int[]".
	 * </p>
	 */
	public String tipoRetornoFuncao = null;

	/**
	 * <p>
	 * Tipo de variavel que esta tentar guardar (de forma errada) o valor de
	 * retorno da funcao.
	 * </p>
	 * 
	 * <p>
	 * So pode ter os valores "int" ou "int[]".
	 * </p>
	 */
	public String tipoDeVariavel = null;

	/**
	 * Uma excepcao que representa uma assign entre uma variavel e um retorno de
	 * uma funcao de tipos incompativeis.
	 * 
	 * @param tipoRetorno
	 *            O tipo de retorno da funcao
	 * @param tipoVariavel
	 *            O tipo da variavel que seria usada para guardar o valor de
	 *            retorno da funcao
	 */
	public WrongFunctionReturnException(String tipoRetorno, String tipoVariavel) {
		tipoRetornoFuncao = tipoRetorno;
		tipoDeVariavel = tipoVariavel;
	}
}

class FunctionNotFoundInModuleException extends Exception {
	private static final long serialVersionUID = 7451253798194904310L;

	/** Nome da funcao que esta em falta */
	public String functionName = null;

	/**
	 * <p>
	 * Uma excepcao que representa uma chamada a uma funcao que nao esta
	 * definida neste modulo.
	 * </p>
	 * 
	 * <p>
	 * Esta excepcao so e lancada para a chamada de funcoes que nao existam
	 * <b>neste</b> modulo. Se a chamada for a uma funcao que esteja definida
	 * noutro modulo, assume-se que ela existe.
	 * </p>
	 * 
	 * <p>
	 * Por exemplo, neste codigo e lancada esta excepcao:
	 * </p>
	 * 
	 * <pre>
	 * module foo {
	 *     function main() {
	 *         bar();
	 *     }
	 * }
	 * </pre>
	 * 
	 * <p>
	 * Mas neste codigo nao e lancada nenhuma excepcao:
	 * </p>
	 * 
	 * <pre>
	 * module foo {
	 *     function main() {
	 *         xpto.bar();
	 *     }
	 * }
	 * </pre>
	 * 
	 * @param functionName
	 *            O nome da funcao que nao esta definida
	 */
	public FunctionNotFoundInModuleException(String functionName) {
		this.functionName = functionName;
	}
}

class WrongFunctionPrototypeException extends Exception {
	private static final long serialVersionUID = -3979992420232537129L;

	/** Mensagem do erro. */
	public String mensagem = null;

	/**
	 * <p>
	 * Uma excepcao que representa uma incoerencia entre o prototipo de uma
	 * funcao (o seu valor de retorno e os seus argumentos) e os valores usados
	 * numa chamada a ela (tambem neste caso sao considerados o tipo da varivel
	 * que vai guardar o resultado, bem como os tipos de todos os argumentos da
	 * chamada).
	 * </p>
	 * 
	 * <p>
	 * Um exemplo que lanca um erro, pois o tipo da variavel onde se vai guardar
	 * o valor de retorno nao e igual ao tipo de retorno da funcao chamada:
	 * </p>
	 * 
	 * <pre>
	 * module err10 {
	 *     function b[]=f1(a) {
	 *         b=[100];
	 *         b[0]=1;
	 *         b[99]=1;
	 *     }
	 * 
	 *     function a=f2(c) {
	 *         a=f1(c);  // semantic error: line 10, return of function 'f1' is an array
	 *     }
	 * }
	 * </pre>
	 * 
	 * <p>
	 * Outro exemplo onde se lanca esta excepcao, pois a funcao e chamada com
	 * uma variavel do tipo "int" como segundo argumento, quando o prototipo da
	 * funcao indica que o segundo argumento da funcao deve ser "int[]"
	 * </p>
	 * 
	 * <pre>
	 * module err9 {
	 *     b=10;
	 * 
	 *     function f2(a, c) {
	 *         a=f1(c, b);  // semantic error: line 6, second argument of function 'f1' must be an array
	 *     }
	 * 
	 *     function b=f1(a, c[]) {
	 *         b=a+c[0];
	 *     }
	 * }
	 * </pre>
	 * 
	 * @param mensagem
	 *            A mensagem de erro que deve ser mostrada
	 */
	public WrongFunctionPrototypeException(String mensagem) {
		this.mensagem = mensagem;
	}
}
